 /*******************************************************************************
  * Copyright (c) 2000, 2007 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal;

 import java.util.ArrayList ;
 import java.util.Collections ;
 import java.util.Enumeration ;
 import java.util.HashMap ;
 import java.util.List ;
 import java.util.Map ;
 import java.util.Vector ;

 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.graphics.Cursor;
 import org.eclipse.swt.graphics.Point;
 import org.eclipse.swt.graphics.Rectangle;
 import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Shell;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.IViewReference;
 import org.eclipse.ui.IWorkbenchPartReference;
 import org.eclipse.ui.IWorkbenchPreferenceConstants;
 import org.eclipse.ui.PlatformUI;
 import org.eclipse.ui.internal.dnd.AbstractDropTarget;
 import org.eclipse.ui.internal.dnd.DragUtil;
 import org.eclipse.ui.internal.dnd.IDragOverListener;
 import org.eclipse.ui.internal.dnd.IDropTarget;
 import org.eclipse.ui.internal.misc.StringMatcher;
 import org.eclipse.ui.presentations.IStackPresentationSite;

 /**
  * A perspective presentation is a collection of parts with a layout. Each part
  * is parented to a main window, so you can create more than one presentation
  * on a set of parts and change the layout just by activating / deactivating a
  * presentation.
  *
  * In addition, the user can change the position of any part by mouse
  * manipulation (drag & drop). If a part is removed, we leave a placeholder
  * behind to indicate where it goes should the part be added back.
  */
 public class PerspectiveHelper {
     private WorkbenchPage page;
     
     private Perspective perspective;

     private Composite parentWidget;

     private ViewSashContainer mainLayout;
     
     private PartStack maximizedStack;

     /**
      * If there is a ViewStack maximized on shutdown the id is
      * cached and restored into this field on 'restoreState'.
      * This is then used to bash the ViewStack's presentation state
      * into the correct value on activation (the startup life-cycle
      * is such that we have to use this 'latch' because the window
      * state isn't valid until the activate happens.
      */
     private String maximizedStackId;

     private ArrayList detachedWindowList = new ArrayList (1);

     private ArrayList detachedPlaceHolderList = new ArrayList (1);

     /**
      * Maps a stack's id to its current bounds
      * this is used to capture the current bounds of all
      * stacks -before- starting a maximize (since the
      * iterative 'minimize' calls cause the intial stack's
      * bounds to change.
      */
     private Map boundsMap = new HashMap ();

     private boolean detachable = false;

     private boolean active = false;

     // key is the LayoutPart object, value is the PartDragDrop object
 //private IPartDropListener partDropListener;

     private static final int MIN_DETACH_WIDTH = 150;

     private static final int MIN_DETACH_HEIGHT = 250;

     protected ActualDropTarget dropTarget;
     
     private IDragOverListener dragTarget = new IDragOverListener() {

         public IDropTarget drag(Control currentControl, Object draggedObject,
                 Point position, final Rectangle dragRectangle) {
             
             if (!(draggedObject instanceof ViewPane || draggedObject instanceof ViewStack)) {
                 return null;
             }
             final LayoutPart part = (LayoutPart) draggedObject;

             if (part.getWorkbenchWindow() != page.getWorkbenchWindow()) {
                 return null;
             }

             if (dropTarget == null) {
                 dropTarget = new ActualDropTarget(part, dragRectangle);
             } else {
                 dropTarget.setTarget(part, dragRectangle);
             }
             
             return dropTarget;
         }

     };

     private final class ActualDropTarget extends AbstractDropTarget {
         private LayoutPart part;

         private Rectangle dragRectangle;

         private ActualDropTarget(LayoutPart part, Rectangle dragRectangle) {
             super();
             setTarget(part, dragRectangle);
         }

         /**
          * @param part
          * @param dragRectangle
          * @since 3.1
          */
         private void setTarget(LayoutPart part, Rectangle dragRectangle) {
             this.part = part;
             this.dragRectangle = dragRectangle;
         }

         public void drop() {

             Shell shell = part.getShell();
             if (shell.getData() instanceof DetachedWindow) {
                 // only one tab folder in a detach window, so do window
 // move
 if (part instanceof ViewStack) {
                     shell.setLocation(dragRectangle.x,
                             dragRectangle.y);
                     return;
                 }
                 // if only one view in tab folder then do a window move
 ILayoutContainer container = part.getContainer();
                 if (container instanceof ViewStack) {
                     if (((ViewStack) container).getItemCount() == 1) {
                         shell.setLocation(dragRectangle.x,
                                 dragRectangle.y);
                         return;
                     }
                 }
             }

             // If layout is modified always zoom out.
 if (isZoomed()) {
                 zoomOut();
             }
             // do a normal part detach
 detach(part, dragRectangle.x, dragRectangle.y);
         }

         public Cursor getCursor() {
             return DragCursors.getCursor(DragCursors.OFFSCREEN);
         }
     }

     private class MatchingPart implements Comparable {
         String pid;

         String sid;

         LayoutPart part;

         boolean hasWildcard;

         int len;

         MatchingPart(String pid, String sid, LayoutPart part) {
             this.pid = pid;
             this.sid = sid;
             this.part = part;
             this.len = (pid == null ? 0 : pid.length())
                     + (sid == null ? 0 : sid.length());
             this.hasWildcard = (pid != null && pid
                     .indexOf(PartPlaceholder.WILD_CARD) != -1)
                     || (sid != null && sid.indexOf(PartPlaceholder.WILD_CARD) != -1);
         }

         public int compareTo(Object a) {
             // specific ids always outweigh ids with wildcards
 MatchingPart ma = (MatchingPart) a;
             if (this.hasWildcard && !ma.hasWildcard) {
                 return -1;
             }
             if (!this.hasWildcard && ma.hasWildcard) {
                 return 1;
             }
             // if both are specific or both have wildcards, simply compare based on length
 return ma.len - this.len;
         }
     }

     
     /**
      * Constructs a new object.
      */
     public PerspectiveHelper(WorkbenchPage workbenchPage,
             ViewSashContainer mainLayout, Perspective perspective) {
         this.page = workbenchPage;
         this.mainLayout = mainLayout;
         this.perspective = perspective;
         
         // Views can be detached if the feature is enabled (true by default,
 // use the plug-in customization file to disable), and if the platform
 // supports detaching.

         final IPreferenceStore store = PlatformUI.getPreferenceStore();
         this.detachable = store.getBoolean(IWorkbenchPreferenceConstants.ENABLE_DETACHED_VIEWS);

         if (this.detachable) {
             // Check if some arbitrary Composite supports reparenting. If it
 // doesn't, views cannot be detached.

             Composite client = workbenchPage.getClientComposite();
             if (client == null) {
                 // The workbench page is not initialized. I don't think this can happen,
 // but if it does, silently set detachable to false.
 this.detachable = false;
             } else {
                 Composite testChild = new Composite(client, SWT.NONE);
                 this.detachable = testChild.isReparentable();
                 testChild.dispose();
             }
         }
     }

     /**
      * Show the presentation.
      */
     public void activate(Composite parent) {

         if (active) {
             return;
         }

         parentWidget = parent;

         // Activate main layout
 // make sure all the views have been properly parented
 Vector children = new Vector ();
         collectViewPanes(children, mainLayout.getChildren());
         Enumeration itr = children.elements();
         while (itr.hasMoreElements()) {
             LayoutPart part = (LayoutPart) itr.nextElement();
             part.reparent(parent);
         }
         mainLayout.createControl(parent);
         mainLayout.setActive(true);

         // Open the detached windows.
 for (int i = 0, length = detachedWindowList.size(); i < length; i++) {
             DetachedWindow dwindow = (DetachedWindow) detachedWindowList.get(i);
             dwindow.open();
         }

         enableAllDrag();

         // Ensure that the maximized stack's presentation state is correct
 if (maximizedStackId != null) {
             LayoutPart part = findPart(maximizedStackId);
             if (part instanceof PartStack) {
                 maximizedStack = (PartStack) part;
                 maximizedStackId = null;
             }
         }
         
         // NOTE: we only handle ViewStacks here; Editor Stacks are handled by the
 // perspective
 if (maximizedStack instanceof ViewStack) {
             maximizedStack.setPresentationState(IStackPresentationSite.STATE_MAXIMIZED);
         }
         
         active = true;
     }

     /**
      * Adds a part to the presentation. If a placeholder exists for the part
      * then swap the part in. Otherwise, add the part in the bottom right
      * corner of the presentation.
      */
     public void addPart(LayoutPart part) {
         
         // Look for a placeholder.
 PartPlaceholder placeholder = null;
         LayoutPart testPart = null;
         String primaryId = part.getID();
         String secondaryId = null;

         IViewReference ref = null;
         if (part instanceof ViewPane) {
             ViewPane pane = (ViewPane) part;
             ref = (IViewReference) pane.getPartReference();
             secondaryId = ref.getSecondaryId();
         }
         if (secondaryId != null) {
             testPart = findPart(primaryId, secondaryId);
         } else {
             testPart = findPart(primaryId);
         }

         // validate the testPart
 if (testPart != null && testPart instanceof PartPlaceholder) {
             placeholder = (PartPlaceholder) testPart;
         }

         // If there is no placeholder do a simple add. Otherwise, replace the
 // placeholder if its not a pattern matching placholder
 if (placeholder == null) {
             part.reparent(mainLayout.getParent());
             LayoutPart relative = mainLayout.findBottomRight();
             if (relative != null && relative instanceof ILayoutContainer) {
                 ILayoutContainer stack = (ILayoutContainer)relative;
                 if (stack.allowsAdd(part)) {
                     mainLayout.stack(part, stack);
                 } else {
                     mainLayout.add(part);
                 }
             } else {
                 mainLayout.add(part);
             }
         } else {
             ILayoutContainer container = placeholder.getContainer();
             if (container != null) {
                 if (container instanceof DetachedPlaceHolder) {
                     //Create a detached window add the part on it.
 DetachedPlaceHolder holder = (DetachedPlaceHolder) container;
                     detachedPlaceHolderList.remove(holder);
                     container.remove(testPart);
                     DetachedWindow window = new DetachedWindow(page);
                     detachedWindowList.add(window);
                     window.create();
                     part.createControl(window.getShell());
                     // Open window.
 window.getShell().setBounds(holder.getBounds());
                     window.open();
                     // add part to detached window.
 ViewPane pane = (ViewPane) part;
                     window.add(pane);
                     LayoutPart otherChildren[] = holder.getChildren();
                     for (int i = 0; i < otherChildren.length; i++) {
                         part.getContainer().add(otherChildren[i]);
                     }
                 } else {
                     // show parent if necessary
 if (container instanceof ContainerPlaceholder) {
                         ContainerPlaceholder containerPlaceholder = (ContainerPlaceholder) container;
                         ILayoutContainer parentContainer = containerPlaceholder
                                 .getContainer();
                         container = (ILayoutContainer) containerPlaceholder
                                 .getRealContainer();
                         if (container instanceof LayoutPart) {
                             parentContainer.replace(containerPlaceholder,
                                     (LayoutPart) container);
                         }
                         containerPlaceholder.setRealContainer(null);
                     }

                     // reparent part.
 if (!(container instanceof ViewStack)) {
                         // We don't need to reparent children of PartTabFolders since they will automatically
 // reparent their children when they become visible. This if statement used to be
 // part of an else branch. Investigate if it is still necessary.
 part.reparent(mainLayout.getParent());
                     }

                     // see if we should replace the placeholder
 if (placeholder.hasWildCard()) {
                         if (container instanceof PartSashContainer) {
                             ((PartSashContainer) container)
                                     .addChildForPlaceholder(part, placeholder);
                         } else {
                             container.add(part);
                         }
                     } else {
                         container.replace(placeholder, part);
                     }
                 }
             }
         }
     }
     
     /**
      * Attaches a part that was previously detached to the mainLayout.
      *
      * @param ref
      */
     public void attachPart(IViewReference ref) {
         ViewPane pane = (ViewPane)((WorkbenchPartReference)ref).getPane();
         
         // Restore any maximized part before re-attaching.
 // Note that 'getMaximizedStack != null' implies 'useNewMinMax'
 if (getMaximizedStack() != null) {
             getMaximizedStack().setState(IStackPresentationSite.STATE_RESTORED);
         }
         
         derefPart(pane);
         addPart(pane);
         bringPartToTop(pane);
         pane.setFocus();
     }

     /**
      * Return whether detachable parts can be supported.
      */
     public boolean canDetach() {
         return detachable;
     }

     /**
      * Bring a part forward so it is visible.
      *
      * @return true if the part was brought to top, false if not.
      */
     public boolean bringPartToTop(LayoutPart part) {
         ILayoutContainer container = part.getContainer();
         if (container != null && container instanceof PartStack) {
             PartStack folder = (PartStack) container;
             if (folder.getSelection() != part) {
                 folder.setSelection(part);
                 return true;
             }
         }
         return false;
     }

     /**
      * Returns true if the given part is visible.
      * A part is visible if it's top-level (not in a tab folder) or if it is the top one
      * in a tab folder.
      */
     public boolean isPartVisible(IWorkbenchPartReference partRef) {
         LayoutPart foundPart;
         if (partRef instanceof IViewReference) {
             foundPart = findPart(partRef.getId(), ((IViewReference) partRef).getSecondaryId());
         } else {
             foundPart = findPart(partRef.getId());
         }
         if (foundPart == null) {
             return false;
         }
         if (foundPart instanceof PartPlaceholder) {
             return false;
         }

         ILayoutContainer container = foundPart.getContainer();
         
         if (container instanceof ContainerPlaceholder) {
             return false;
         }

         if (container instanceof ViewStack) {
             ViewStack folder = (ViewStack) container;
             PartPane visiblePart = folder.getSelection();
             if (visiblePart == null) {
                 return false;
             }
             return partRef.equals(visiblePart.getPartReference());
         }
         return true;
     }

     /**
      * Returns true is not in a tab folder or if it is the top one in a tab
      * folder.
      */
     public boolean willPartBeVisible(String partId) {
         return willPartBeVisible(partId, null);
     }

     public boolean willPartBeVisible(String partId, String secondaryId) {
         LayoutPart part = findPart(partId, secondaryId);
         if (part == null) {
             return false;
         }
         ILayoutContainer container = part.getContainer();
         if (container != null && container instanceof ContainerPlaceholder) {
             container = (ILayoutContainer) ((ContainerPlaceholder) container)
                     .getRealContainer();
         }

         if (container != null && container instanceof ViewStack) {
             ViewStack folder = (ViewStack) container;
             if (folder.getSelection() == null) {
                 return false;
             }
             return part.getCompoundId().equals(
                     folder.getSelection().getCompoundId());
         }
         return true;
     }

     /**
      * Answer a list of the PartPlaceholder objects.
      */
     private PartPlaceholder[] collectPlaceholders() {
         // Scan the main window.
 PartPlaceholder[] results = collectPlaceholders(mainLayout
                 .getChildren());

         // Scan each detached window.
 if (detachable) {
             for (int i = 0, length = detachedWindowList.size(); i < length; i++) {
                 DetachedWindow win = (DetachedWindow) detachedWindowList.get(i);
                 PartPlaceholder[] moreResults = collectPlaceholders(win
                         .getChildren());
                 if (moreResults.length > 0) {
                     int newLength = results.length + moreResults.length;
                     PartPlaceholder[] newResults = new PartPlaceholder[newLength];
                     System.arraycopy(results, 0, newResults, 0, results.length);
                     System.arraycopy(moreResults, 0, newResults,
                             results.length, moreResults.length);
                     results = newResults;
                 }
             }
         }
         return results;
     }

     /**
      * Answer a list of the PartPlaceholder objects.
      */
     private PartPlaceholder[] collectPlaceholders(LayoutPart[] parts) {
         PartPlaceholder[] result = new PartPlaceholder[0];

         for (int i = 0, length = parts.length; i < length; i++) {
             LayoutPart part = parts[i];
             if (part instanceof ILayoutContainer) {
                 // iterate through sub containers to find sub-parts
 PartPlaceholder[] newParts = collectPlaceholders(((ILayoutContainer) part)
                         .getChildren());
                 PartPlaceholder[] newResult = new PartPlaceholder[result.length
                         + newParts.length];
                 System.arraycopy(result, 0, newResult, 0, result.length);
                 System.arraycopy(newParts, 0, newResult, result.length,
                         newParts.length);
                 result = newResult;
             } else if (part instanceof PartPlaceholder) {
                 PartPlaceholder[] newResult = new PartPlaceholder[result.length + 1];
                 System.arraycopy(result, 0, newResult, 0, result.length);
                 newResult[result.length] = (PartPlaceholder) part;
                 result = newResult;
             }
         }

         return result;
     }

     /**
      * Answer a list of the view panes.
      */
     public void collectViewPanes(List result) {
         // Scan the main window.
 collectViewPanes(result, mainLayout.getChildren());

         // Scan each detached window.
 if (detachable) {
             for (int i = 0, length = detachedWindowList.size(); i < length; i++) {
                 DetachedWindow win = (DetachedWindow) detachedWindowList.get(i);
                 collectViewPanes(result, win.getChildren());
             }
         }
     }

     /**
      * Answer a list of the view panes.
      */
     private void collectViewPanes(List result, LayoutPart[] parts) {
         for (int i = 0, length = parts.length; i < length; i++) {
             LayoutPart part = parts[i];
             if (part instanceof ViewPane) {
                 result.add(part);
             } else if (part instanceof ILayoutContainer) {
                 collectViewPanes(result, ((ILayoutContainer) part)
                         .getChildren());
             }
         }
     }

     /**
      * Hide the presentation.
      */
     public void deactivate() {
         if (!active) {
             return;
         }

         disableAllDrag();

         // Reparent all views to the main window
 Composite parent = mainLayout.getParent();
         Vector children = new Vector ();
         collectViewPanes(children, mainLayout.getChildren());

         for (int i = 0, length = detachedWindowList.size(); i < length; i++) {
             DetachedWindow window = (DetachedWindow) detachedWindowList.get(i);
             collectViewPanes(children, window.getChildren());
         }

         // *** Do we even need to do this if detached windows not supported?
 Enumeration itr = children.elements();
         while (itr.hasMoreElements()) {
             LayoutPart part = (LayoutPart) itr.nextElement();
             part.reparent(parent);
         }
         
         // Dispose main layout.

         mainLayout.setActive(false);
         
         // Dispose the detached windows
 for (int i = 0, length = detachedWindowList.size(); i < length; i++) {
             DetachedWindow window = (DetachedWindow) detachedWindowList.get(i);
             window.close();
         }
         
         active = false;
     }
     
     public void dispose() {
         mainLayout.dispose();
         mainLayout.disposeSashes();
     }
     
     /**
      * Writes a description of the layout to the given string buffer.
      * This is used for drag-drop test suites to determine if two layouts are the
      * same. Like a hash code, the description should compare as equal iff the
      * layouts are the same. However, it should be user-readable in order to
      * help debug failed tests. Although these are english readable strings,
      * they should not be translated or equality tests will fail.
      * <p>
      * This is only intended for use by test suites.
      * </p>
      *
      * @param buf
      */
     public void describeLayout(StringBuffer buf) {

         if (detachable) {
             if(detachedWindowList.size() != 0){
                 buf.append("detachedWindows ("); //$NON-NLS-1$

                 for (int i = 0, length = detachedWindowList.size(); i < length; i++) {
                     DetachedWindow window = (DetachedWindow) detachedWindowList.get(i);
                     LayoutPart[] children = window.getChildren();
                     if(children.length != 0){
                         buf.append("dWindow ("); //$NON-NLS-1$
 for(int j = 0; j < children.length; j++){
                             buf.append(((ViewPane)children[j]).getViewReference().getPartName());
                             if(j < (children.length - 1)) {
                                 buf.append(", "); //$NON-NLS-1$
 }
                         }
                         buf.append(")"); //$NON-NLS-1$
 }
                     
                 }
                 buf.append("), "); //$NON-NLS-1$
 }
         }
             
         getLayout().describeLayout(buf);
     }

     /**
      * Deref a given part. Deconstruct its container as required. Do not remove
      * drag listeners.
      */
     /* package */void derefPart(LayoutPart part) {
         if (part instanceof ViewPane) {
             IViewReference ref = ((ViewPane) part).getViewReference();
             if (perspective.isFastView(ref)) {
                 // Special check: if it's a fast view then it's actual ViewStack
 // may only contain placeholders and the stack is represented in
 // the presentation by a container placeholder...make sure the
 // PartPlaceHolder for 'ref' is removed from the ViewStack
 String id = perspective.getFastViewManager().getIdForRef(ref);
                 LayoutPart parentPart = findPart(id, null);
                 if (parentPart instanceof ContainerPlaceholder) {
                     ViewStack vs = (ViewStack) ((ContainerPlaceholder)parentPart).getRealContainer();
                     LayoutPart[] kids = vs.getChildren();
                     for (int i = 0; i < kids.length; i++) {
                         if (kids[i] instanceof PartPlaceholder) {
                             if (ref.getId().equals(kids[i].id))
                                 vs.remove(kids[i]);
                         }
                     }
                 }
                 perspective.getFastViewManager().removeViewReference(ref, true, true);
             }
         }

         // Get vital part stats before reparenting.
 ILayoutContainer oldContainer = part.getContainer();
         boolean wasDocked = part.isDocked();
         Shell oldShell = part.getShell();

         // Reparent the part back to the main window
 part.reparent(mainLayout.getParent());

         // Update container.
 if (oldContainer == null) {
             return;
         }

         oldContainer.remove(part);

         LayoutPart[] children = oldContainer.getChildren();
         if (wasDocked) {
             boolean hasChildren = (children != null) && (children.length > 0);
             if (hasChildren) {
                 // make sure one is at least visible
 int childVisible = 0;
                 for (int i = 0; i < children.length; i++) {
                     if (children[i].getControl() != null) {
                         childVisible++;
                     }
                 }

                 // none visible, then reprarent and remove container
 if (oldContainer instanceof ViewStack) {
                     ViewStack folder = (ViewStack) oldContainer;
                     
                     // Is the part in the trim?
 boolean inTrim = false;
                     // Safety check...there may be no FastViewManager
 if (perspective.getFastViewManager() != null)
                         inTrim = perspective.getFastViewManager().getFastViews(folder.getID()).size() > 0;
                         
                     if (childVisible == 0 && !inTrim) {
                         ILayoutContainer parentContainer = folder
                                 .getContainer();
                         for (int i = 0; i < children.length; i++) {
                             folder.remove(children[i]);
                             parentContainer.add(children[i]);
                         }
                         hasChildren = false;
                     } else if (childVisible == 1) {
                         LayoutTree layout = mainLayout.getLayoutTree();
                         layout = layout.find(folder);
                         layout.setBounds(layout.getBounds());
                     }
                 }
             }

             if (!hasChildren) {
                 // There are no more children in this container, so get rid of
 // it
 if (oldContainer instanceof LayoutPart) {
                     LayoutPart parent = (LayoutPart) oldContainer;
                     ILayoutContainer parentContainer = parent.getContainer();
                     if (parentContainer != null) {
                         parentContainer.remove(parent);
                         parent.dispose();
                     }
                 }
             }
         } else if (!wasDocked) {
             if (children == null || children.length == 0) {
                 // There are no more children in this container, so get rid of
 // it
 // Turn on redraw again just in case it was off.
 //oldShell.setRedraw(true);
 DetachedWindow w = (DetachedWindow)oldShell.getData();
                 oldShell.close();
                 detachedWindowList.remove(w);
             } else {
                 // There are children. If none are visible hide detached
 // window.
 boolean allInvisible = true;
                 for (int i = 0, length = children.length; i < length; i++) {
                     if (!(children[i] instanceof PartPlaceholder)) {
                         allInvisible = false;
                         break;
                     }
                 }
                 if (allInvisible) {
                     DetachedPlaceHolder placeholder = new DetachedPlaceHolder(
                             "", //$NON-NLS-1$
 oldShell.getBounds());
                     for (int i = 0, length = children.length; i < length; i++) {
                         oldContainer.remove(children[i]);
                         children[i].setContainer(placeholder);
                         placeholder.add(children[i]);
                     }
                     detachedPlaceHolderList.add(placeholder);
                     DetachedWindow w = (DetachedWindow)oldShell.getData();
                     oldShell.close();
                     detachedWindowList.remove(w);
                 }
             }
         }

     }

     /**
      * Create a detached window containing a part.
      */
     private void detach(LayoutPart source, int x, int y) {

         // Detaching is disabled on some platforms ..
 if (!detachable) {
             return;
         }

         LayoutPart part = source.getPart();
         // Calculate detached window size.
 Point size = part.getSize();
         if (size.x == 0 || size.y == 0) {
             ILayoutContainer container = part.getContainer();
             if (container instanceof LayoutPart) {
                 size = ((LayoutPart) container).getSize();
             }
         }
         int width = Math.max(size.x, MIN_DETACH_WIDTH);
         int height = Math.max(size.y, MIN_DETACH_HEIGHT);

         // Create detached window.
 DetachedWindow window = new DetachedWindow(page);
         detachedWindowList.add(window);

         // Open window.
 window.create();
         window.getShell().setBounds(x, y, width, height);
         window.open();

         if (part instanceof ViewStack) {
             window.getShell().setRedraw(false);
             parentWidget.setRedraw(false);
             LayoutPart visiblePart = ((ViewStack) part).getSelection();
             LayoutPart children[] = ((ViewStack) part).getChildren();
             for (int i = 0; i < children.length; i++) {
                 if (children[i] instanceof ViewPane) {
                     // remove the part from its current container
 derefPart(children[i]);
                     // add part to detached window.
 ViewPane pane = (ViewPane) children[i];
                     window.add(pane);
                 }
             }
             if (visiblePart != null) {
                 bringPartToTop(visiblePart);
                 visiblePart.setFocus();
             }
             window.getShell().setRedraw(true);
             parentWidget.setRedraw(true);
         } else {
             // remove the part from its current container
 derefPart(part);
             // add part to detached window.
 ViewPane pane = (ViewPane) part;
             window.add(pane);
             part.setFocus();
         }

     }
     
     /**
      * Detached a part from the mainLayout. Presently this does not use placeholders
      * since the current implementation is not robust enough to remember a view's position
      * in more than one root container. For now the view is simply derefed and will dock
      * in the default position when attachPart is called.
      *
      * By default parts detached this way are set to float on top of the workbench
      * without docking. It is assumed that people that want to drag a part back onto
      * the WorkbenchWindow will detach it via drag and drop.
      *
      * @param ref
      */
     public void detachPart(IViewReference ref) {
         ViewPane pane = (ViewPane)((WorkbenchPartReference)ref).getPane();
         if (canDetach() && pane != null) {
             Rectangle bounds = pane.getParentBounds();
             detach(pane, bounds.x ,bounds.y);
         }
     }

     /**
      * Create a detached window containing a part.
      */
     public void addDetachedPart(LayoutPart part) {
         // Calculate detached window size.
 Rectangle bounds = parentWidget.getShell().getBounds();
         bounds.x = bounds.x + (bounds.width - 300) / 2;
         bounds.y = bounds.y + (bounds.height - 300) / 2;
         
         addDetachedPart(part, bounds);
     }
     
     public void addDetachedPart(LayoutPart part, Rectangle bounds) {
         // Detaching is disabled on some platforms ..
 if (!detachable) {
             addPart(part);
             return;
         }
         
         // Create detached window.
 DetachedWindow window = new DetachedWindow(page);
         detachedWindowList.add(window);
         window.create();

         // add part to detached window.
 part.createControl(window.getShell());
         ViewPane pane = (ViewPane) part;
         window.add(pane);

         // Open window.
 window.getShell().setBounds(bounds.x, bounds.y, bounds.width, bounds.height);
         window.open();

         part.setFocus();
         
     }

     /**
      * disableDragging.
      */
     private void disableAllDrag() {
         DragUtil.removeDragTarget(null, dragTarget);
     }

     /**
      * enableDragging.
      */
     private void enableAllDrag() {
         DragUtil.addDragTarget(null, dragTarget);
     }

     /**
      * Find the first part with a given ID in the presentation.
      * Wild cards now supported.
      */
     private LayoutPart findPart(String id) {
         return findPart(id, null);
     }

     /**
      * Find the first part that matches the specified
      * primary and secondary id pair. Wild cards
      * are supported.
      */
     public LayoutPart findPart(String primaryId, String secondaryId) {
         // check main window.
 ArrayList matchingParts = new ArrayList ();
         LayoutPart part = (secondaryId != null) ? findPart(primaryId,
                 secondaryId, mainLayout.getChildren(), matchingParts)
                 : findPart(primaryId, mainLayout.getChildren(), matchingParts);
         if (part != null) {
             return part;
         }

         // check each detached windows.
 for (int i = 0, length = detachedWindowList.size(); i < length; i++) {
             DetachedWindow window = (DetachedWindow) detachedWindowList.get(i);
             part = (secondaryId != null) ? findPart(primaryId, secondaryId,
                     window.getChildren(), matchingParts) : findPart(primaryId,
                     window.getChildren(), matchingParts);
             if (part != null) {
                 return part;
             }
         }
         for (int i = 0; i < detachedPlaceHolderList.size(); i++) {
             DetachedPlaceHolder holder = (DetachedPlaceHolder) detachedPlaceHolderList
                     .get(i);
             part = (secondaryId != null) ? findPart(primaryId, secondaryId,
                     holder.getChildren(), matchingParts) : findPart(primaryId,
                     holder.getChildren(), matchingParts);
             if (part != null) {
                 return part;
             }
         }

         // sort the matching parts
 if (matchingParts.size() > 0) {
             Collections.sort(matchingParts);
             MatchingPart mostSignificantPart = (MatchingPart) matchingParts
                    .get(0);
            if (mostSignificantPart != null) {
                return mostSignificantPart.part;
            }
        }

        // Not found.
 return null;
    }

    /**
     * Find the first part with a given ID in the presentation.
     */
    private LayoutPart findPart(String id, LayoutPart[] parts,
            ArrayList matchingParts) {
        for (int i = 0, length = parts.length; i < length; i++) {
            LayoutPart part = parts[i];
            // check for part equality, parts with secondary ids fail
 if (part.getID().equals(id)) {
                if (part instanceof ViewPane) {
                    ViewPane pane = (ViewPane) part;
                    IViewReference ref = (IViewReference) pane
                            .getPartReference();
                    if (ref.getSecondaryId() != null) {
                        continue;
                    }
                }
                return part;
            }
            // check pattern matching placeholders
 else if (part instanceof PartPlaceholder
                    && ((PartPlaceholder) part).hasWildCard()) {
                StringMatcher sm = new StringMatcher(part.getID(), true, false);
                if (sm.match(id)) {
                    matchingParts
                            .add(new MatchingPart(part.getID(), null, part));
                }
            } else if (part instanceof EditorSashContainer) {
                // Skip.
 } else if (part instanceof ILayoutContainer) {
                part = findPart(id, ((ILayoutContainer) part).getChildren(),
                        matchingParts);
                if (part != null) {
                    return part;
                }
            }
        }
        return null;
    }

    /**
     * Find the first part that matches the specified
     * primary and secondary id pair. Wild cards
     * are supported.
     */
    private LayoutPart findPart(String primaryId, String secondaryId,
            LayoutPart[] parts, ArrayList matchingParts) {
        for (int i = 0, length = parts.length; i < length; i++) {
            LayoutPart part = parts[i];
            // check containers first
 if (part instanceof ILayoutContainer) {
                LayoutPart testPart = findPart(primaryId, secondaryId,
                        ((ILayoutContainer) part).getChildren(), matchingParts);
                if (testPart != null) {
                    return testPart;
                }
            }
            // check for view part equality
 if (part instanceof ViewPane) {
                ViewPane pane = (ViewPane) part;
                IViewReference ref = (IViewReference) pane.getPartReference();
                if (ref.getId().equals(primaryId)
                        && ref.getSecondaryId() != null
                        && ref.getSecondaryId().equals(secondaryId)) {
                    return part;
                }
            }
            // check placeholders
 else if ((parts[i] instanceof PartPlaceholder)) {
                String id = part.getID();

                // optimization: don't bother parsing id if it has no separator -- it can't match
 String phSecondaryId = ViewFactory.extractSecondaryId(id);
                if (phSecondaryId == null) {
                    // but still need to check for wildcard case
 if (id.equals(PartPlaceholder.WILD_CARD)) {
                        matchingParts.add(new MatchingPart(id, null, part));
                    }
                    continue;
                }

                String phPrimaryId = ViewFactory.extractPrimaryId(id);
                // perfect matching pair
 if (phPrimaryId.equals(primaryId)
                        && phSecondaryId.equals(secondaryId)) {
                    return part;
                }
                // check for partial matching pair
 StringMatcher sm = new StringMatcher(phPrimaryId, true, false);
                if (sm.match(primaryId)) {
                    sm = new StringMatcher(phSecondaryId, true, false);
                    if (sm.match(secondaryId)) {
                        matchingParts.add(new MatchingPart(phPrimaryId,
                                phSecondaryId, part));
                    }
                }
            } else if (part instanceof EditorSashContainer) {
                // Skip.
 }
        }
        return null;
    }

    /**
     * Returns true if a placeholder exists for a given ID.
     */
    public boolean hasPlaceholder(String id) {
        return hasPlaceholder(id, null);
    }

    /**
     * Returns true if a placeholder exists for a given ID.
     * @since 3.0
     */
    public boolean hasPlaceholder(String primaryId, String secondaryId) {
        LayoutPart testPart;
        if (secondaryId == null) {
            testPart = findPart(primaryId);
        } else {
            testPart = findPart(primaryId, secondaryId);
        }
        return (testPart != null && testPart instanceof PartPlaceholder);
    }

    /**
     * Returns the layout container.
     */
    public ViewSashContainer getLayout() {
        return mainLayout;
    }

    /**
     * Gets the active state.
     */
    public boolean isActive() {
        return active;
    }

    /**
     * Returns whether the presentation is zoomed.
     *
     * <strong>NOTE:</strong> As of 3.3 this method should always return 'false'
     * when using the new min/max behavior. It is only used for
     * legacy 'zoom' handling.
     */
    public boolean isZoomed() {
        return mainLayout.getZoomedPart() != null;
    }

    /**
     * @return The currently maxmized stack (if any)
     */
    public PartStack getMaximizedStack() {
        return maximizedStack;
    }

    /**
     * Sets the currently maximized stack. Used for query
     * and 'unZoom' purposes in the 3.3 presentation.
     *
     * @param stack The newly maximized stack
     */
    public void setMaximizedStack(PartStack stack) {
        if (stack == maximizedStack)
            return;
        
        maximizedStack = stack;
    }
    
    /**
     * Returns the ratio that should be used when docking the given source
     * part onto the given target
     *
     * @param source newly added part
     * @param target existing part being dragged over
     * @return the final size of the source part (wrt the current size of target)
     * after it is docked
     */
    public static float getDockingRatio(LayoutPart source, LayoutPart target) {
        if ((source instanceof ViewPane || source instanceof ViewStack)
                && target instanceof EditorSashContainer) {
            return 0.25f;
        }
        return 0.5f;
    }

    /**
     * Returns whether changes to a part will affect zoom. There are a few
     * conditions for this .. - we are zoomed. - the part is contained in the
     * main window. - the part is not the zoom part - the part is not a fast
     * view - the part and the zoom part are not in the same editor workbook
     * - the part and the zoom part are not in the same view stack.
     */
    public boolean partChangeAffectsZoom(LayoutPart pane) {
        return pane.isObscuredByZoom();
    }
    
    /**
     * Remove all references to a part.
     */
    public void removePart(LayoutPart part) {

        // Reparent the part back to the main window
 Composite parent = mainLayout.getParent();
        part.reparent(parent);

        // Replace part with a placeholder
 ILayoutContainer container = part.getContainer();
        if (container != null) {
            String placeHolderId = part.getPlaceHolderId();
            container.replace(part, new PartPlaceholder(placeHolderId));

            // If the parent is root we're done. Do not try to replace
 // it with placeholder.
 if (container == mainLayout) {
                return;
            }

            // If the parent is empty replace it with a placeholder.
 LayoutPart[] children = container.getChildren();
            if (children != null) {
                boolean allInvisible = true;
                for (int i = 0, length = children.length; i < length; i++) {
                    if (!(children[i] instanceof PartPlaceholder)) {
                        allInvisible = false;
                        break;
                    }
                }
                if (allInvisible && (container instanceof LayoutPart)) {
                    // what type of window are we in?
 LayoutPart cPart = (LayoutPart) container;
                    //Window oldWindow = cPart.getWindow();
 boolean wasDocked = cPart.isDocked();
                    Shell oldShell = cPart.getShell();
                    if (wasDocked) {
                        
                        // PR 1GDFVBY: ViewStack not disposed when page
 // closed.
 if (container instanceof ViewStack) {
                            ((ViewStack) container).dispose();
                        }
                        
                        // replace the real container with a
 // ContainerPlaceholder
 ILayoutContainer parentContainer = cPart.getContainer();
                        ContainerPlaceholder placeholder = new ContainerPlaceholder(
                                cPart.getID());
                        placeholder.setRealContainer(container);
                        parentContainer.replace(cPart, placeholder);
                        
                    } else {
                        DetachedPlaceHolder placeholder = new DetachedPlaceHolder(
                                "", oldShell.getBounds()); //$NON-NLS-1$
 for (int i = 0, length = children.length; i < length; i++) {
                            children[i].getContainer().remove(children[i]);
                            children[i].setContainer(placeholder);
                            placeholder.add(children[i]);
                        }
                        detachedPlaceHolderList.add(placeholder);
                        DetachedWindow w = (DetachedWindow)oldShell.getData();
                        oldShell.close();
                        detachedWindowList.remove(w);
                    }
                }
            }
        }
    }

    /**
     * Add a part to the presentation.
     *
     * Note: unlike all other LayoutParts, PartPlaceholders will still point to
     * their parent container even when it is inactive. This method relies on this
     * fact to locate the parent.
     */
    public void replacePlaceholderWithPart(LayoutPart part) {
        
        // Look for a PartPlaceholder that will tell us how to position this
 // object
 PartPlaceholder[] placeholders = collectPlaceholders();
        for (int i = 0, length = placeholders.length; i < length; i++) {
            if (placeholders[i].getCompoundId().equals(part.getCompoundId())) {
                // found a matching placeholder which we can replace with the
 // new View
 ILayoutContainer container = placeholders[i].getContainer();
                if (container != null) {
                    if (container instanceof ContainerPlaceholder) {
                        // One of the children is now visible so replace the
 // ContainerPlaceholder with the real container
 ContainerPlaceholder containerPlaceholder = (ContainerPlaceholder) container;
                        ILayoutContainer parentContainer = containerPlaceholder
                                .getContainer();
                        container = (ILayoutContainer) containerPlaceholder
                                .getRealContainer();
                        if (container instanceof LayoutPart) {
                            parentContainer.replace(containerPlaceholder,
                                    (LayoutPart) container);
                        }
                        containerPlaceholder.setRealContainer(null);
                    }
                    container.replace(placeholders[i], part);
                    return;
                }
            }
        }

    }

    /**
     * @see org.eclipse.ui.IPersistable
     */
    public IStatus restoreState(IMemento memento) {
        // Restore main window.
 IMemento childMem = memento
                .getChild(IWorkbenchConstants.TAG_MAIN_WINDOW);
        IStatus r = mainLayout.restoreState(childMem);

        // Restore each floating window.
 if (detachable) {
            IMemento detachedWindows[] = memento
                    .getChildren(IWorkbenchConstants.TAG_DETACHED_WINDOW);
            for (int nX = 0; nX < detachedWindows.length; nX++) {
                DetachedWindow win = new DetachedWindow(page);
                detachedWindowList.add(win);
                win.restoreState(detachedWindows[nX]);
            }
            IMemento childrenMem[] = memento
                    .getChildren(IWorkbenchConstants.TAG_HIDDEN_WINDOW);
            for (int i = 0, length = childrenMem.length; i < length; i++) {
                DetachedPlaceHolder holder = new DetachedPlaceHolder(
                        "", new Rectangle(0, 0, 0, 0)); //$NON-NLS-1$
 holder.restoreState(childrenMem[i]);
                detachedPlaceHolderList.add(holder);
            }
        }
        
        // Get the cached id of the currently maximized stack
 maximizedStackId = childMem.getString(IWorkbenchConstants.TAG_MAXIMIZED);
        
        return r;
    }

    /**
     * @see org.eclipse.ui.IPersistable
     */
    public IStatus saveState(IMemento memento) {
        // Persist main window.
 IMemento childMem = memento
                .createChild(IWorkbenchConstants.TAG_MAIN_WINDOW);
        IStatus r = mainLayout.saveState(childMem);

        if (detachable) {
            // Persist each detached window.
 for (int i = 0, length = detachedWindowList.size(); i < length; i++) {
                DetachedWindow window = (DetachedWindow) detachedWindowList
                        .get(i);
                childMem = memento
                        .createChild(IWorkbenchConstants.TAG_DETACHED_WINDOW);
                window.saveState(childMem);
            }
            for (int i = 0, length = detachedPlaceHolderList.size(); i < length; i++) {
                DetachedPlaceHolder holder = (DetachedPlaceHolder) detachedPlaceHolderList
                        .get(i);
                childMem = memento
                        .createChild(IWorkbenchConstants.TAG_HIDDEN_WINDOW);
                holder.saveState(childMem);
            }
        }
        
        // Write out the id of the maximized (View) stack (if any)
 // NOTE: we only write this out if it's a ViewStack since the
 // Editor Area is handled by the perspective
 if (maximizedStack instanceof ViewStack) {
            childMem.putString(IWorkbenchConstants.TAG_MAXIMIZED, maximizedStack.getID());
        }
        else if (maximizedStackId != null) {
            // Maintain the cache if the perspective has never been activated
 childMem.putString(IWorkbenchConstants.TAG_MAXIMIZED, maximizedStackId);
        }
        
        return r;
    }

    /**
     * Zoom in on a particular layout part.
     */
    public void zoomIn(IWorkbenchPartReference ref) {
        PartPane pane = ((WorkbenchPartReference) ref).getPane();


        parentWidget.setRedraw(false);
        try {
            pane.requestZoomIn();
        } finally {
            parentWidget.setRedraw(true);
        }
    }

    /**
     * Zoom out.
     */
    public void zoomOut() {
        // New 3.3 behavior
 if (Perspective.useNewMinMax(perspective)) {
             if (maximizedStack != null)
                 maximizedStack.setState(IStackPresentationSite.STATE_RESTORED);
             return;
        }
        
        LayoutPart zoomPart = mainLayout.getZoomedPart();
        if (zoomPart != null) {
            zoomPart.requestZoomOut();
        }
    }

    /**
     * Forces the perspective to have no zoomed or minimized parts.
     * This is used when switching to the 3.3 presentation...
     */
    public void forceNoZoom() {
        // Ensure that nobody's zoomed
 zoomOut();
        
        // Now, walk the layout ensuring that nothing is minimized
 LayoutPart[] kids = mainLayout.getChildren();
        for (int i = 0; i < kids.length; i++) {
            if (kids[i] instanceof ViewStack) {
                ((ViewStack)kids[i]).setMinimized(false);
            }
            else if (kids[i] instanceof EditorSashContainer) {
                LayoutPart[] editorStacks = ((EditorSashContainer)kids[i]).getChildren();
                for (int j = 0; j < editorStacks.length; j++) {
                    if (editorStacks[j] instanceof EditorStack) {
                        ((EditorStack)editorStacks[j]).setMinimized(false);
                    }
                }
            }
        }
    }

    /**
     * Captures the current bounds of all ViewStacks and the editor
     * area and puts them into an ID -> Rectangle map. This info is
     * used to cache the bounds so that we can correctly place minimized
     * stacks during a 'maximized' operation (where the iterative min's
     * affect the current layout while being performed.
     */
    public void updateBoundsMap() {
        boundsMap.clear();
        
        // Walk the layout gathering the current bounds of each stack
 // and the editor area
 LayoutPart[] kids = mainLayout.getChildren();
        for (int i = 0; i < kids.length; i++) {
            if (kids[i] instanceof ViewStack) {
                ViewStack vs = (ViewStack)kids[i];
                boundsMap.put(vs.getID(), vs.getBounds());
            }
            else if (kids[i] instanceof EditorSashContainer) {
                EditorSashContainer esc = (EditorSashContainer)kids[i];
                boundsMap.put(esc.getID(), esc.getBounds());
            }
        }
    }

    /**
     * Resets the bounds map so that it won't interfere with normal minimize
     * operayions
     */
    public void resetBoundsMap() {
        boundsMap.clear();
    }
    
    public Rectangle getCachedBoundsFor(String id) {
        return (Rectangle) boundsMap.get(id);
    }
}

